package com.hmall.search.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.domain.vo.PageVO;
import com.hmall.search.service.ISearchService;
import org.apache.http.HttpHost;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements ISearchService {

    //索引库名称
    private static final String INDEX_NAME = "items";

    private RestHighLevelClient client;

    @Autowired
    private ItemClient itemClient;

    public SearchServiceImpl() {
        client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.12.128:9200")));
    }

    @Override
    public void saveItemById(Long itemId) {
        try {
            //1、根据商品id查询商品信息
            ItemDTO itemDTO = itemClient.queryItemById(itemId);
            if (itemDTO != null) {
                //2、将得到的商品信息封装成ItemDoc
                ItemDoc itemDoc = BeanUtils.copyBean(itemDTO, ItemDoc.class);
                //3、创建 创建文档的 的请求对象
                IndexRequest request = new IndexRequest(INDEX_NAME).id(itemDoc.getId().toString());
                //4、将ItemDoc对象转换成JSON
                String jsonStr = JSONUtil.toJsonStr(itemDoc);
                //5、设置source参数
                request.source(jsonStr, XContentType.JSON);
                //6、发送请求
                client.index(request, RequestOptions.DEFAULT);
            }
        } catch (IOException e) {
            throw new RuntimeException("更新es中的商品失败！参数：" + itemId, e);
        }
    }

    @Override
    public void deleteItemById(Long itemId) {
        //1、创建删除文档的请求对象
        DeleteRequest deleteRequest = new DeleteRequest(INDEX_NAME, itemId.toString());
        //2、发送请求
        try {
            client.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("删除es中的商品失败！参数：" + itemId, e);
        }
    }

    @Override
    public PageVO<ItemDoc> search(ItemPageQuery query) {
        PageVO<ItemDoc> pageVO = PageVO.empty(0L, 0L);
        try {
            //1、创建查询的请求对象
            SearchRequest request = new SearchRequest(INDEX_NAME);
            //2、设置请求参数
            //创建bool查询
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolean isHighlight = false;
            //设置搜索关键字
            if (StrUtil.isNotBlank(query.getKey())) {
                boolQuery.must(QueryBuilders.matchQuery("name", query.getKey()));
                isHighlight = true;
            }
            //设置商品分类过滤查询
            if (StrUtil.isNotBlank(query.getCategory())) {
                boolQuery.filter(QueryBuilders.matchQuery("category", query.getCategory()));
            }
            //设置商品品牌过滤查询
            if (StrUtil.isNotBlank(query.getBrand())) {
                boolQuery.filter(QueryBuilders.matchQuery("brand", query.getBrand()));
            }
            //设置价格区间过滤查询
            if (query.getMinPrice() != null) {
                boolQuery.filter(QueryBuilders.rangeQuery("price").from(query.getMinPrice()));
            }
            if (query.getMaxPrice() != null) {
                boolQuery.filter(QueryBuilders.rangeQuery("price").to(query.getMaxPrice()));
            }
            //设置高亮
            if (isHighlight) {
                request.source().highlighter(SearchSourceBuilder.highlight()
                        .field("name")
                        .preTags("<em>")
                        .postTags("</em>"));
            }
            //设置分页
            Integer pageNo = query.getPageNo();
            Integer pageSize = query.getPageSize();
            request.source().from((pageNo - 1) * pageSize).size(pageSize);
            //设置排序
            if (StrUtil.isNotBlank(query.getSortBy())) {
                request.source().sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
            } else {
                //request.source().sort("updateTime", SortOrder.DESC);
            }
            //设置查询对象
            //request.source().query(boolQuery);
            //依据算法函数，按照广告进行加权算分
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQuery,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    ScoreFunctionBuilders.weightFactorFunction(10)),
                    }).boostMode(CombineFunction.MULTIPLY);
            request.source().query(functionScoreQueryBuilder);

            //3. 发送请求
            SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
            //4. 处理响应结果
            SearchHits searchHits = searchResponse.getHits();
            //总记录数
            long total = searchHits.getTotalHits().value;
            pageVO.setTotal(total);
            //通过页大小和总记录数计算总页数
            long pages = (total + pageSize - 1) / pageSize;
            pageVO.setPages(pages);
            List<ItemDoc> list = new ArrayList<>(pageSize);
            SearchHit[] hits = searchHits.getHits();
            for (SearchHit hit : hits) {
                String JsonStr = hit.getSourceAsString();
                ItemDoc itemDoc = JSONUtil.toBean(JsonStr, ItemDoc.class);
                if (isHighlight) {
                    HighlightField highlightField = hit.getHighlightFields().get("name");
                    if (highlightField != null){
                        String name = highlightField.fragments()[0].string();
                        itemDoc.setName(name);
                    }
                }
                list.add(itemDoc);
            }
            pageVO.setList(list);
            return pageVO;
        } catch (IOException e) {
            throw new RuntimeException("查询es中的商品失败！参数：" + query, e);
        }
    }

    @Override
    public Map<String, List<String>> filter(ItemPageQuery query) {
        Map<String, List<String>> resultMap = new HashMap<>();
        try {
            //只有当分类或品牌没有选择的时才有必要去查对应的数据
            if (StrUtil.isBlank(query.getCategory()) || StrUtil.isBlank(query.getBrand())) {
                //1、创建查询的请求对象
                SearchRequest request = new SearchRequest(INDEX_NAME);
                //2、设置请求参数
                //是否需要查询分类聚合数据
                boolean isNeedCategoryAgg = true;
                //是否需要查询品牌聚合数据
                boolean isNeedBrandAgg = true;
                //设置不返回商品详情
                request.source().size(0);
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //设置搜索关键字
                if (StrUtil.isNotBlank(query.getKey())) {
                   boolQuery.must(QueryBuilders.matchQuery("name", query.getKey()));
                }
                if (StrUtil.isNotBlank(query.getCategory())){
                    boolQuery.filter(QueryBuilders.termQuery("category", query.getCategory()));
                    isNeedCategoryAgg = false;
                }
                if (StrUtil.isNotBlank(query.getBrand())){
                    boolQuery.filter(QueryBuilders.termQuery("brand", query.getBrand()));
                    isNeedBrandAgg = false;
                }
                if (query.getMinPrice() != null){
                    boolQuery.filter(QueryBuilders.rangeQuery("price").from(query.getMinPrice()));
                }
                if (query.getMaxPrice() != null){
                    boolQuery.filter(QueryBuilders.rangeQuery("price").to(query.getMaxPrice()));
                }
                request.source().query(boolQuery);
                //设置聚合
                if (isNeedCategoryAgg){
                    request.source().aggregation(AggregationBuilders
                            .terms("category_agg")
                            .field("category")
                            .size(20));
                }
                if (isNeedBrandAgg){
                    request.source().aggregation(AggregationBuilders
                            .terms("brand_agg")
                            .field("brand")
                            .size(20));
                }

                //3、发送请求
                SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
                //4、处理响应结果
                Aggregations aggregations = searchResponse.getAggregations();
                Terms categoryAgg = aggregations.get("category_agg");
                if (categoryAgg != null) {
                    List<String> categoryList = new ArrayList<>();
                    for (Terms.Bucket bucket : categoryAgg.getBuckets()){
                        categoryList.add(bucket.getKeyAsString());
                    }
                    resultMap.put("category", categoryList);
                }
                Terms brandAgg = aggregations.get("brand_agg");
                if (brandAgg != null) {
                    List<String> brandList = new ArrayList<>();
                    for (Terms.Bucket bucket : brandAgg.getBuckets()){
                        brandList.add(bucket.getKeyAsString());
                    }
                    resultMap.put("brand", brandList);
                }
                return resultMap;
            }
        } catch (IOException e) {
            throw new RuntimeException("查询es中的商品分类、品牌列表失败！参数：" + query, e);
        }
        return CollUtils.emptyMap();
    }
}
